/*
 * Created on 26-Nov-2004
 *
 */
package org.hibernate.cfg;

import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Element;
import org.hibernate.MappingException;
import org.hibernate.cfg.reveng.DefaultReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.MappingsDatabaseCollector2;
import org.hibernate.cfg.reveng.ReverseEngineeringStrategy;
import org.hibernate.engine.Mapping;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Table;
import org.hibernate.type.Type;
import org.hibernate.util.ConfigHelper;

/**
 * 数据库反转配置类
 * 
 * 建议，使用工厂方法来创建；
 * 
 * 支持EJB3注解：修改了父类Configuration为AnnotationConfiguration；
 * 增加了工厂方法：createByXmlClasspath,createByProperties,createByPropertiesClasspath；
 * 根据配置文件自动初始化ReverseEngineeringSettings；
 * 扩展了默认反转引擎DefaultReverseEngineeringStrategy，可以配置需要忽视的类名前缀；
 * 扩展了MappingsDatabaseCollector，使其支持将表与视图区分保存；
 * 默认使用ImprovedNamingStrategy命名策略；
 * 
 * @author iPan
 * @version 2013-12-11
 */
public class JDBCMetaDataConfiguration extends AnnotationConfiguration {

	/**
	 * serialVersionUID
	 */
	private static final long serialVersionUID = 8575235947428049984L;
	private static final Log log = LogFactory.getLog(JDBCMetaDataConfiguration.class);
	private ReverseEngineeringStrategy revEngStrategy = new DefaultReverseEngineeringStrategy();
	/** Key为小写字母表名 */
	private Map<String, Table> tableMap = null;
	/** Key为小写字母视图名 */
	private Map<String, Table> viewMap = null;
	
	public JDBCMetaDataConfiguration () {
		super();
		this.setNamingStrategy(ImprovedNamingStrategy.INSTANCE);
	}

	@SuppressWarnings("rawtypes")
	protected void secondPassCompileForeignKeys(Table table, Set done) throws MappingException {
		super.secondPassCompileForeignKeys(table, done);
		// TODO: doing nothing to avoid creating foreignkeys which is NOT
		// actually in the database.
	}
	
	public void configure(String driver, String url, String userName, String passWord, String schema, String dialect) {
		configure(driver, url, userName, passWord, null, schema, dialect);
	}
	
	public void configure(String driver, String url, String userName, String passWord, String catalog, String schema, String dialect) {
		this.setProperty(Environment.DRIVER, driver);
		this.setProperty(Environment.URL, url);
		this.setProperty(Environment.USER, userName);
		this.setProperty(Environment.PASS, passWord);
		this.setProperty(Environment.DEFAULT_CATALOG, catalog);
		this.setProperty(Environment.DEFAULT_SCHEMA, schema);
		this.setProperty(Environment.DIALECT, dialect);
	}

	public void readFromJDBC() {
		readFromJDBC(null, null); // JDBCBinder默认会取配置文件的信息
	}
	
	public void readFromJDBC(String catalog, String schema) {
		JDBCBinder binder = new JDBCBinder(this, buildSettings(), createMappings(), revEngStrategy);
		MappingsDatabaseCollector2 collector = (MappingsDatabaseCollector2) binder.readFromDatabase(catalog, schema, buildMapping(this));
		this.tableMap = collector.getTableMap();
		this.viewMap = collector.getViewMap();
	}

	static private Mapping buildMapping(final Configuration cfg) {
		return new Mapping() {
			/**
			 * Returns the identifier type of a mapped class
			 */
			public Type getIdentifierType(String persistentClass) throws MappingException {
				PersistentClass pc = cfg.getClassMapping(persistentClass);
				if (pc == null)
					throw new MappingException("persistent class not known: " + persistentClass);
				return pc.getIdentifier().getType();
			}

			public String getIdentifierPropertyName(String persistentClass) throws MappingException {
				final PersistentClass pc = cfg.getClassMapping(persistentClass);
				if (pc == null)
					throw new MappingException("persistent class not known: " + persistentClass);
				if (!pc.hasIdentifierProperty())
					return null;
				return pc.getIdentifierProperty().getName();
			}

			public Type getReferencedPropertyType(String persistentClass, String propertyName) throws MappingException {
				final PersistentClass pc = cfg.getClassMapping(persistentClass);
				if (pc == null)
					throw new MappingException("persistent class not known: " + persistentClass);
				Property prop = pc.getProperty(propertyName);
				if (prop == null)
					throw new MappingException("property not known: " + persistentClass + '.' + propertyName);
				return prop.getType();
			}

			public IdentifierGeneratorFactory getIdentifierGeneratorFactory() {
				return null;
			}
		};
	}

	private boolean ignoreconfigxmlmapppings = true;
	// set to true and fk's that are part of a primary key will just be mapped
	// as the raw value and as a readonly property. if false, it will be
	// <many-to-one-key-property
	private boolean preferBasicCompositeIds = true;

	/**
	 * If true, compositeid's will not create key-many-to-one and
	 * non-updatable/non-insertable many-to-one will be created instead.
	 * 
	 * @return
	 */
	public boolean preferBasicCompositeIds() {
		return preferBasicCompositeIds;
	}

	public void setPreferBasicCompositeIds(boolean flag) {
		preferBasicCompositeIds = flag;
	}

	protected void parseMappingElement(Element subelement, String name) {
		if (!ignoreconfigxmlmapppings) {
			super.parseMappingElement(subelement, name);
		} else {
			log.info("Ignoring " + name + " mapping");
		}
	}

	public void setReverseEngineeringStrategy(ReverseEngineeringStrategy reverseEngineeringStrategy) {
		this.revEngStrategy = reverseEngineeringStrategy;
	}

	public ReverseEngineeringStrategy getReverseEngineeringStrategy() {
		return revEngStrategy;
	}

	public Map<String, Table> getTableMap() {
		return tableMap;
	}

	public Map<String, Table> getViewMap() {
		return viewMap;
	}

	/**
	 * 根据属性文件的类路径创建.
	 */
	public static JDBCMetaDataConfiguration createByPropertiesClasspath(String name) {
		JDBCMetaDataConfiguration configuration = new JDBCMetaDataConfiguration();
		// 初始化配置信息
		Properties properties = ConfigHelper.getConfigProperties(name);
		configuration.addProperties(properties);
		initSettings(configuration);
		return configuration;
	}

	/**
	 * 根据属性文件创建.
	 */
	public static JDBCMetaDataConfiguration createByProperties(Properties properties) {
		JDBCMetaDataConfiguration configuration = new JDBCMetaDataConfiguration();
		// 初始化配置信息
		configuration.addProperties(properties);
		initSettings(configuration);
		return configuration;
	}
	
	/**
	 * 根据XML文件的类路径创建.
	 */
	public static JDBCMetaDataConfiguration createByXmlClasspath(String xml) {
		JDBCMetaDataConfiguration configuration = new JDBCMetaDataConfiguration();
		// 初始化配置信息
		configuration.configure(xml);
		initSettings(configuration);
		return configuration;
	}
	
	private static void initSettings(JDBCMetaDataConfiguration configuration) {
		// 初始化反转引擎
		ReverseEngineeringStrategy reveStrategy = configuration.getReverseEngineeringStrategy();
		reveStrategy.getSettings().loadByProperties(configuration.getProperties());
		// 对catalog、schema特殊处理（hibernate生成数据库标识的时候是通过判断null的，所以空字符串容易引起错误！）
		if ("".equals(configuration.getProperty(Environment.DEFAULT_CATALOG))) {
			configuration.getProperties().remove(Environment.DEFAULT_CATALOG);
		}
		if ("".equals(configuration.getProperty(Environment.DEFAULT_SCHEMA))) {
			configuration.getProperties().remove(Environment.DEFAULT_SCHEMA);
		}
	}
}
